package com.eugenes.concurrency.util;

public class SynchronizedQueue<T> {

    private final T[] data;
    private final int capacity;

    private int head;
    private int tail;

    // volatile for correct get method
    private volatile int size;

    private final Object waitGet = new Object();
    private final Object waitPush = new Object();

    public SynchronizedQueue(int capacity) {
        if (capacity <= 0)
            throw new IllegalArgumentException("Capacity should be greater than zero.");
        
        // We take care about this array.
        @SuppressWarnings("unchecked")
        final T[] _data = (T[]) new Object[capacity];
        data = _data;
        
        this.head = this.tail = this.size = 0;
        this.capacity = capacity;
    }

    public int getCapacity() {
        return capacity;
    }

    public int getSize() {
        return size;
    }

    private void notifyAllObject(Object obj) {
        synchronized (obj) {
            obj.notifyAll();
        }
    }

    private synchronized boolean privatePush(T element) {
        if (capacity == size)
            return false;

        data[tail++] = element;
        if (tail == capacity)
            tail = 0;
        ++size;
        return true;
    }

    public void push(T element) throws InterruptedException {
        if (element == null)
            throw new NullPointerException("Element cannot be null");

        if (!privatePush(element)) {
            synchronized (waitGet) {
                while (!privatePush(element)) {
                    waitGet.wait();
                }
            }
        }
        notifyAllObject(waitPush);
    }

    private synchronized T privateGet() {
        if (size == 0)
            return null;

        T result = data[head++];
        if (head == capacity)
            head = 0;
        --size;
        return result;
    }

    public T get() throws InterruptedException {
        T result;
        if ((result = privateGet()) == null) {
            synchronized (waitPush) {
                while ((result = privateGet()) == null) {
                    waitPush.wait();
                }
            }
        }
        notifyAllObject(waitGet);
        return result;
    }
}
